from django.core.exceptions            import ObjectDoesNotExist
from django.db.models                  import Q
from django.utils.datastructures       import SortedDict
from sitepal.sitebase.conf             import properties
from sitepal.sitebase.management.setup import register,ObjectSetup,ListSetup
from sitepal.sitemenu.models           import Menu,MenuView,MenuLink
from sitepal.sitemenu.models           import MENU_NORMAL_ITEM,MENU_CALLBACK,MENU_SUGGESTED_ITEM
from sitepal.sitemenu.models           import MENU_LOCAL_TASK,MENU_DEFAULT_LOCAL_TASK
from sitepal.sitemenu.models.flags     import MENU_IS_LOCAL_TASK,MENU_VISIBLE_IN_BREADCRUMB
from sitepal.sitemenu.validators       import PathValidator

class MenuSetup(ObjectSetup):
    def __init__(self):
        ObjectSetup.__init__(self,"menus",Menu.objects)
    
    def syncobject(self,name,data):
        obj             = self.initobject(name,data)
        obj.title       = data['title']
        obj.description = data.get('description')
        return obj

class ViewSetup(ObjectSetup):
    FLAGS = {
        'NORMAL'            : MENU_NORMAL_ITEM,
        'CALLBACK'          : MENU_CALLBACK,
        'SUGGESTED'         : MENU_SUGGESTED_ITEM,
        'LOCAL_TASK'        : MENU_LOCAL_TASK,
        'DEFAULT_LOCAL_TASK': MENU_DEFAULT_LOCAL_TASK,
    }

    def __init__(self):
        ObjectSetup.__init__(self,"views",MenuView.objects,name_attr='path')

    def get_menu(self,name=None):
        if name:
            return Menu.objects.get(name__exact=name)
        return Menu.objects.get(name__exact='navigation')

    # First pass: Separate callbacks from paths, making paths ready for
    # matching. Fill some default values. Fitness is calculated on save
    def syncobject(self,name,data):
        obj             = self.initobject(name,data)
        obj.name        = data.get('name')
        obj.title       = data['title']
        obj.description = data.get('description')
        obj.view_func   = data.get('view_func')
        obj.view_args   = data.get('view_args')
        obj.menu        = self.get_menu(data.get('menu'))
        if 'weight' in data:
            obj.weight = data['weight']
        if 'type' in data:
            obj.type_flags = self.FLAGS[data['type'].upper()]
        else:
            obj.type_flags = MENU_NORMAL_ITEM
        data['_meta_tab']     = bool(obj.type_flags & MENU_IS_LOCAL_TASK)
        data['_meta_visible'] = bool(obj.type_flags & MENU_VISIBLE_IN_BREADCRUMB)
        return obj

    # Sort menu view by number of path parts
    def prepare_syncmeta(self):
        super(ViewSetup,self).prepare_syncmeta()
        self.parts_fit_masks = set()
        self.synced_objects.keyOrder.sort(key=lambda x: len(PathValidator.RE_SPLIT.split(x)))
        self.menu_links = SortedDict()

    # Set fitness mask in property
    def cleanup_syncmeta(self):
        super(ViewSetup,self).cleanup_syncmeta()
        masks = list(self.parts_fit_masks)
        masks.sort()
        masks.reverse()
        properties.set_value('menu-views-parts-fit-masks',masks,admin=True)
        self.menu_links.keyOrder.sort(key=lambda x: len(PathValidator.RE_SPLIT.split(x)))
        for created,link in self.menu_links.itervalues():
            if created or not link.customized:
                link.save()
        for link in MenuLink.objects.filter(external=False,view__isnull=True):
            pass
        del self.parts_fit_masks
        del self.menu_links

    # Second pass: Apply inheritance rules
    def syncmeta(self,obj,data):
        super(ViewSetup,self).syncmeta(obj,data)
        self.parts_fit_masks.add(obj.parts_fit)
        if not data['_meta_tab']:
            obj.task_parent = None
            obj.task_root   = obj.path
        parts = PathValidator.RE_SPLIT.split(obj.path)
        for i in xrange(1,len(parts)):
            parent_path = "/".join(parts[:-i])
            if parent_path in self.synced_objects:
                parent,parent_data = self.synced_objects[parent_path]
                if not obj.task_parent:
                    obj.task_parent = parent_path
                if not obj.task_root and not parent_data['_meta_tab']:
                    obj.task_root = parent_path
                if not obj.view_func and parent.view_func:
                    obj.view_func = parent.view_func
                    obj.view_args = parent.view_args
        if data['_meta_visible']:
            self.menu_links[obj.path] = self.get_or_create_link(obj,data)
        obj.save()

    def get_or_create_link(self,view,data):
        try:
            link    = MenuLink.objects.get(~Q(origin__exact='[user]') & Q(path__exact=view.path))
            created = False
        except ObjectDoesNotExist:
            link         = MenuLink()
            link.site    = self.default_site
            link.owner   = self.default_user
            link.creator = self.default_user
            link.origin  = data['_app_name_']
            link.menu    = self.get_menu()
            link.title   = view.title
            link.path    = view.path
            if view.type_flags == MENU_CALLBACK:
                link.hidden = -1
            elif view.type_flags == MENU_SUGGESTED_ITEM:
                link.hidden = 1
            else:
                link.hidden = 0
            created = True
        return created,link

class SiteMenuSetup(ListSetup):
    def __init__(self):
        ListSetup.__init__(self,"site menu",MenuSetup(),ViewSetup())

register(SiteMenuSetup())
